package com.vtech.ont.tdb.portal;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.vtech.ont.file.OntFileService;
import com.vtech.ont.tdb.config.TdbDataSource;
import org.apache.jena.ontology.OntModel;
import org.apache.jena.query.*;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.riot.RDFDataMgr;
import org.egc.commons.exception.BusinessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * TdbPortalImpl
 *
 * @author houzhiwei
 * @date 2017/7/14 20:29
 * @since 1.0
 */
@Service
public class TdbPortalImpl implements TdbPortal {

    final static Logger log = LoggerFactory.getLogger(TdbPortalImpl.class);

    @Autowired
    @Qualifier("tdbDataSource")
    TdbDataSource tdbDataSource;

    @Autowired
    OntFileService fileService;

    // 必须close
    private Dataset ds = null;

    public TdbPortalImpl() {
        String tdbLoc = tdbDataSource.getTdbLocation();
        String assembler = tdbDataSource.getAssemblerFileLocation();
        if (!Strings.isNullOrEmpty(tdbLoc))
            ds = TdbConnection.connect(tdbLoc);
        else if (!Strings.isNullOrEmpty(assembler))
            ds = TdbConnection.assembleConnect(assembler);
        else
            throw new InvalidPropertyException(TdbDataSource.class,
                                               "jena.tdb.location | jena.tdb.assembler-file",
                                               "Either 'location' or 'assembler-file' must be set properly");
    }

    @Override
    public Dataset getDataset() {
        return this.ds;
    }

    @Override
    public int loadModel(String modelUri, String entryFile, Boolean isOverride) {
        if (isOverride && ds.containsNamedModel(modelUri)) {
            removeModel(modelUri);
        }
        return loadModel(modelUri, entryFile);
    }

    @Override
    public int loadOntModel(String modelUri, String entryFile, Boolean isOverride) {
        if (isOverride && ds.containsNamedModel(modelUri)) {
            removeModel(modelUri);
        }
        return loadOntModel(modelUri, entryFile);
    }

    @Override
    public Model getDefaultModel() {
        ds.begin(ReadWrite.READ);
        Model model;
        try {
            model = ds.getDefaultModel();
            ds.commit();
        } finally {
            ds.end();
        }
        return model;
    }

    @Override
    public boolean isModelExists(String modelUri) {
        String queryStr = "ASK WHERE { GRAPH <" + modelUri + "> { ?s ?p ?o } }";
        ds.begin(ReadWrite.READ);
        Boolean flag = formQueryExecution(queryStr).execAsk();
        ds.end();
        return flag;
    }

    @Override
    public boolean isTDBEmpty() {
        String queryString = "select (count(*) as ?count) { graph ?g { ?s ?p ?o } }";
        ResultSet results = formQueryExecution(queryString).execSelect();
        return results.hasNext();
    }

    @Override
    public Model getNamedModel(String modelUri) {
        return ds.getNamedModel(modelUri);
    }

    @Override
    public int loadDefaultModel(String entryFile) {
        Model model = null;
        checkEntryFile(entryFile);
        ds.begin(ReadWrite.WRITE);
        try {
            model = ds.getDefaultModel();
            model.begin();
            RDFDataMgr.read(model, entryFile);
            model.commit();
            ds.commit();
            return 1;
        } finally {
            if (model != null)
                model.close();
            ds.end();
        }
    }

    @Override
    public void removeModel(String modelUri) {
        if (!ds.isInTransaction())
            ds.begin(ReadWrite.WRITE);
        try {
            ds.removeNamedModel(modelUri);
            ds.commit();
            log.info(modelUri + " 已被移除");
        } finally {
            ds.end();
        }
    }

    @Override
    public List<String> listModels() {
        ds.begin(ReadWrite.READ);
        List<String> uriList = new ArrayList<>();
        try {
            Iterator<String> names = ds.listNames();// DefaultModel没有name
            String name;
            while (names.hasNext()) {
                name = names.next();
                uriList.add(name);
            }
        } finally {
            ds.end();
        }
        return uriList;
    }

    @Override
    public void close() {
        ds.close();
    }

    //------------------------------------------ internal functions ------------------------------------//

    /**
     * check whether entry file is null or empty
     *
     * @param entryFile
     */
    private void checkEntryFile(String entryFile) {
        Preconditions.checkArgument(Strings.isNullOrEmpty(entryFile),
                                    "Ontology model entry file must not be null or empty");
    }

    /**
     * load model
     *
     * @param modelUri
     * @param entryFile
     * @return
     */
    private int loadModel(String modelUri, String entryFile) {
        checkEntryFile(entryFile);
        Model model = null;
        ds.begin(ReadWrite.WRITE);
        try {
            model = ds.getNamedModel(modelUri);// 没有则创建一个，model不会为null
            model.begin();
            RDFDataMgr.read(model, entryFile);
            model.commit();
            ds.commit();
            log.info("本体模型数据已导入!");
            return 1;
        } catch (Exception e) {
            log.info("Load model into TDB failed! " + e.getLocalizedMessage());
            throw new BusinessException(e, "Load model into TDB failed! " + e.getLocalizedMessage());
        } finally {
            if (model != null)
                model.close();
            ds.end();
        }
    }

    /**
     * form Query Execution
     *
     * @param sparqlStr
     * @return
     */
    private QueryExecution formQueryExecution(String sparqlStr) {
        Query query = QueryFactory.create(sparqlStr);
        return QueryExecutionFactory.create(query, ds);
    }

    /**
     * <pre>
     * load ont model
     * user location-mapper file
     * load imports
     * </pre>
     *
     * @param modelUri
     * @param entryFile
     * @return
     */
    private int loadOntModel(String modelUri, String entryFile) {
        checkEntryFile(entryFile);
        OntModel model = ModelFactory.createOntologyModel();// 不支持事务
        ds.begin(ReadWrite.WRITE);
        try {
            model = fileService.loadOntModelWithLocMapper(entryFile);
            ds.addNamedModel(modelUri, model);
            ds.commit();
            log.info(modelUri + " 已导入");
            return 1;
        } catch (Exception e) {
            log.error(e.getLocalizedMessage());
            throw new BusinessException(e, "Load model into TDB failed! " + e.getLocalizedMessage());
        } finally {
            ds.end();
        }
    }

}
